﻿using System;
using System.Collections.Generic;
using Microsoft.Maps.MapControl;
using MyFloridaHouse.libSvDTO;
using System.Linq;

namespace MyFloridaHouse.libSvBingMapsMultiShape
{
    public static class MapMultiShapeTools
    {
        public const double ReductionMaxZoomLevel = 14;

        // roundByLevel rule of thumb, two more than zeros after decimal place of points/pixel
//        private static int[] roundByLevel = { 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, 8 }; //initial staging release
        private static int[] roundByLevel = { 2, 2, 2, 4, 4, 7, 7, 7, 5, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, 8 };
        private static double[] latPointsPerPixelPerLevel = 
        { 0.62199348573949800000
        , 0.31099674286974900000
        , 0.15549837143487400000
        , 0.07774918571743720000
        , 0.03887459285871860000
        , 0.01943729642935930000
        , 0.00971864821467965000
        , 0.00485932410733983000
        , 0.00242966205366991000
        , 0.00121483102683496000
        , 0.00060741551341747800
        , 0.00030370775670873900
        , 0.00015185387835437000
        , 0.00007592693917718480
        , 0.00003796346958859240
        , 0.00001898173479429620
        , 0.00000949086739714810
        , 0.00000474543369857405
        , 0.00000237271684928702
        , 0.00000118635842464351
        , 0.000000593179212321755

        };
        private static double[] lonPointsPerPixelPerLevel =
        {
          0.70312500000000000000
        , 0.35156250000000000000
        , 0.17578125000000000000
        , 0.08789062500000000000
        , 0.04394531250000000000
        , 0.02197265625000000000
        , 0.01098632812500000000
        , 0.00549316406250000000
        , 0.00274658203125000000
        , 0.00137329101562500000
        , 0.00068664550781250000
        , 0.00034332275390625000
        , 0.00017166137695312500
        , 0.00008583068847656250
        , 0.00004291534423828120
        , 0.00002145767211914060
        , 0.00001072883605957030
        , 0.00000536441802978516
        , 0.00000268220901489258
        , 0.00000134110450744629
        , 0.00000067055225372314
        };


        public static MapMultiShapeDTO ReducePointsByZoomLevel(MapMultiShapeDTO shp, double zoomLevel)
        {
            if (null == shp)
            {
                throw new Exception("Tried to reduce points for a shape without any points, not a fatal error - OK to continue");
            }
            if (zoomLevel > ReductionMaxZoomLevel)
            {
                return shp;
            }
            else
            {
                MapMultiShapeDTO reducedShape = new MapMultiShapeDTO();
                reducedShape.shapeType = shp.shapeType;
                reducedShape.shapeList = new List<MapMultiShapeEntryDTO>();

                foreach (var subShape in shp.shapeList)
                {
                    MapMultiShapeEntryDTO newSubShape = new MapMultiShapeEntryDTO();
                    newSubShape.shapeType = subShape.shapeType;
                    newSubShape.ExteriorRing = MapMultiShapeTools.ReduceRingPointsByZoomLevel(subShape.ExteriorRing, zoomLevel);
                    newSubShape.InteriorRings = new List<List<PointDTO>>();
                    foreach (var intRing in subShape.InteriorRings)
                    {
                        newSubShape.InteriorRings.Add(MapMultiShapeTools.ReduceRingPointsByZoomLevel(intRing, zoomLevel));
                    }
                    reducedShape.shapeList.Add(newSubShape);
                }

                return reducedShape;
            }
        }

        private static List<PointDTO> ReduceRingPointsByZoomLevel(List<PointDTO> pointlist, double zoomLevel)
        {
            int level = Convert.ToInt32(zoomLevel) - 1;
            if ((level < 0) | (level > roundByLevel.Length)) throw new Exception("Invalid zoom level sent to ReduceRingPointsByZoomLevel");
            int roundDecimalPoints = roundByLevel[level];

            List<PointDTO> reducedPoints = new List<PointDTO>();

            // convert points to visible resolution, this will create duplicates which we can then remove
            foreach (var pnt in pointlist)
            {
                PointDTO newPoint = new PointDTO();
                newPoint.longitude = Math.Round(Math.Round((pnt.longitude / lonPointsPerPixelPerLevel[level]), 0) * lonPointsPerPixelPerLevel[level], roundDecimalPoints);
                newPoint.latitude = Math.Round(Math.Round((pnt.latitude / latPointsPerPixelPerLevel[level]), 0) * latPointsPerPixelPerLevel[level], roundDecimalPoints);
                reducedPoints.Add(newPoint);
            }

            List<PointDTO> noDuplicateList = MapMultiShapeTools.ReducePoints(reducedPoints, roundDecimalPoints);
            //List<PointDTO> noDuplicateList = reducedPoints; // skip removing dupicates
            return noDuplicateList;
        }

        public static List<PointDTO> ReducePoints(List<PointDTO> pointList, int roundDecimalPoints)
        {


            RemoveIdenticalPointsInList(pointList);
            //LinePointReduction(pointList, roundDecimalPoints);
            return pointList;
        }


        private static void RemoveIdenticalPointsInList(List<PointDTO> pointList)
        {
            // remove duplicates no matter where they are in the list
            //             3
            //             |
            //      --1----24----5--
            // when point 2 & 4 are removed becomes
            //
            //             3
            //           /   \
            //      ----1       5----
            //
            // this also happens
            //
            //             23
            //           /   \
            //      ----1       4----
            // 
            // becomes
            //      ----1-------4----
            // 

            List<PointDTO> listCopy = (from p in pointList
                                       orderby p.latitude, p.longitude
                                       select p).ToList();
            int ndx = 1;
            while (ndx < listCopy.Count - 1)
            {
                if (listCopy[ndx].Compare(listCopy[ndx + 1]))
                {
                    // they are equal, remove both copies of the point
                    pointList.Remove(listCopy[ndx]);
                    pointList.Remove(listCopy[ndx]);
                }
                ndx++;
            }
        }

        private static void LinePointReduction(List<PointDTO> pointList, int roundDecimalPoints)
        {
            // remove the midpoint of any line
            int ndx = 0;
            while (ndx < pointList.Count - 2)
            {
                if (ndx < 0)
                {
                    ndx = 0;
                }

                if ((ndx + 2) < pointList.Count)
                {
                    // take out any midpoints of straight lines, compare slopes of line n to 1 with line 1 to 2
                    double deltaY1 = pointList[ndx].latitude - pointList[ndx + 1].latitude;
                    double deltaY2 = pointList[ndx + 1].latitude - pointList[ndx + 2].latitude;
                    double deltaX1 = pointList[ndx].longitude - pointList[ndx + 1].longitude;
                    double deltaX2 = pointList[ndx + 1].longitude - pointList[ndx + 2].longitude;
                    if (Math.Round((deltaY2 * deltaX1), 6) == Math.Round((deltaY1 * deltaX2), roundDecimalPoints))
                    {
                        // third point out is continuation of a straight line, remove the middle point
                        pointList.RemoveAt(ndx + 1);
                        ndx--;
                    }
                    else
                    {
                        ndx++;
                    }
                }
            }
        }

    }
}
